package com.yixing.tech.system.service.impl;

import com.yixing.tech.common.utils.MongoUtils;
import com.yixing.tech.system.po.SystemUser;
import org.apache.commons.lang3.StringUtils;
import com.yixing.tech.common.base.BaseMongoDao;
import com.yixing.tech.common.base.BusinessException;
import com.yixing.tech.common.base.Page;
import com.yixing.tech.common.base.PageRow;
import com.yixing.tech.common.enums.ComparisonSymbol;
import com.yixing.tech.common.enums.ErrCode;
import com.yixing.tech.system.po.Menu;
import com.yixing.tech.system.po.Role;
import com.yixing.tech.system.po.User;
import com.yixing.tech.system.service.IMenuService;
import com.yixing.tech.common.utils.CommonUtil;
import com.yixing.tech.common.utils.DataUtil;
import com.yixing.tech.common.vo.s3.ButtonVO;
import com.yixing.tech.common.vo.s3.MenuVO;
import com.yixing.tech.common.vo.s3.UserVO;
import com.yixing.tech.common.vo.s3.WebPageVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author yixing tech
 * at 2021/12/31
 * describe：
 */
@Slf4j
@Service
public class MenuServiceImpl implements IMenuService {


    @Resource
    BaseMongoDao baseMongoDao;
    @Resource
    MongoTemplate mongoTemplate;
    @Value("${official-tenant-id}")
    private String officialTenantId;


    @Override
    public void add(MenuVO menuVO) throws BusinessException {
        MenuVO condition = new MenuVO();
        condition.setName(menuVO.getName());
        condition.setTenantId(menuVO.getTenantId());
        condition.setApplication(menuVO.getApplication());
        if (baseMongoDao.exists(condition, Menu.class)) {
            throw new BusinessException(ErrCode.MENU_EXISTED);
        }
        Menu menu = DataUtil.o2o(menuVO, Menu.class);
        menu.setId(null);
        if (menu.getStyle() == null) {
            menu.setStyle(0);
        }
        baseMongoDao.save(menu);
    }

    @Override
    public void batchAdd(List<MenuVO> list) throws BusinessException {

    }

    @Override
    public MenuVO queryById(String id) {
        Menu menu = baseMongoDao.get(id, Menu.class);
        return DataUtil.o2o(menu, MenuVO.class);
    }

    @Override
    public List<MenuVO> queryByIds(List<String> ids) {
        return null;
    }

    @Override
    public MenuVO queryByCondition(MenuVO menuVO) throws BusinessException {
        return null;
    }

    @Override
    public void update(MenuVO menuVO) throws BusinessException {
        MenuVO cond = new MenuVO();
        cond.setId(menuVO.getId());
        cond.setTenantId(menuVO.getTenantId());
        if (!baseMongoDao.exists(cond, Menu.class)) throw new BusinessException(ErrCode.UNAUTHORIZED);

        WebPageVO webPageVO = menuVO.getWebPage();
        if (webPageVO != null && StringUtils.isEmpty(webPageVO.getId())) {
            menuVO.setWebPage(null);
        }
        baseMongoDao.update(DataUtil.o2o(menuVO, Menu.class));
    }

    @Override
    public Page<MenuVO> list(PageRow<MenuVO> pageRow) {
        return null;
    }

    @Override
    public List<MenuVO> list(MenuVO condition) {
        return listNew(condition);
    }

    /**
     * 优化递归查询
     *
     * @param condition
     * @return
     */
    private List<MenuVO> listNew(MenuVO condition) {
        Criteria criteria = Criteria.where("tenantId").in(officialTenantId,condition.getTenantId()).and("isDelete").is(0);
        List<AggregationOperation> aggregations = new ArrayList<>();
        aggregations.add(Aggregation.match(criteria));
        aggregations.add(Aggregation.lookup("webPage", "webPage._id", "_id", "webPage"));
        aggregations.add(Aggregation.unwind("webPage", true));
        aggregations.add(Aggregation.project("tenantId","webPage", "name", "icon", "sort", "style", "parentId", "application", "conditions", "variables"));
        List<MenuVO> menuVOS = mongoTemplate.aggregate(Aggregation.newAggregation(aggregations), Menu.class, MenuVO.class).getMappedResults();
        //平铺转树形
        List<MenuVO> root = new ArrayList<>();
        Map<Object, List<MenuVO>> map = new HashMap<>(100);
        for (MenuVO vo : menuVOS) {
            vo.setIsPrivate(condition.getTenantId().equals(vo.getTenantId()));
            Object pId = vo.getParentId();
            if (ObjectUtils.isEmpty(pId)) {
                root.add(vo);
            } else {
                List<MenuVO> children = map.get(pId);
                if (ObjectUtils.isEmpty(children)) {
                    children = new ArrayList<>();
                    map.put(pId, children);
                }
                children.add(vo);
            }
        }
        for (MenuVO vo : root) {
            setChildren(vo, map);
        }
        root.sort(Comparator.comparing(MenuVO::getSort));
        return root;
    }
    /**
     * 优化递归查询
     *
     * @return
     */
    private void setChildren(MenuVO vo, Map<Object, List<MenuVO>> map) {
        List<MenuVO> children = map.get(vo.getId());
        if(ObjectUtils.isEmpty(children))return;
        children.sort(Comparator.comparing(MenuVO::getSort));
        vo.setChildren(children);
        for (MenuVO child : children) {
            child.setIsPrivate(vo.getIsPrivate());
            setChildren(child, map);
        }
    }


    @Override
    public void deleteById(String id) throws BusinessException {
        baseMongoDao.delete(id, Menu.class);
        mongoTemplate.remove(Query.query(Criteria.where("parentId").is(id)), Menu.class);
    }

    @Override
    public void delete(List<String> ids) throws BusinessException {

    }

    @Override
    public void addChild(MenuVO vo) throws BusinessException {
//        menuDao.addChild(vo);
        add(vo);
    }

    @Override
    public List<MenuVO> list(MenuVO vo, UserVO userVO) throws BusinessException {
        User user = baseMongoDao.get(userVO.getId(), User.class);
        List<MenuVO> menuList = list(vo);
        Role currRole = null;
        if (!user.getIsSuper()) {
            currRole = user.getRole();
            Role selfRole = user.getSelfRole();
            if (currRole != null) {
                currRole = baseMongoDao.get(user.getRole().getId(), Role.class);
            }
            currRole = mergeRole(currRole, selfRole);
            if (null == currRole) throw new BusinessException(ErrCode.USER_NO_ROLE);

        }
        filterMenu(menuList, currRole, user.getIsAdmin(), user.getTenantId());
        return menuList;
    }

    /**
     * 角色权限合并
     *
     * @param currRole
     * @param selfRole
     * @return
     */
    private Role mergeRole(Role currRole, Role selfRole) {
        if (currRole == null) return selfRole;
        if (selfRole == null) return currRole;
        Set<String> menus = new HashSet<>();
        menus.addAll(currRole.getMenuIds());
        menus.addAll(selfRole.getMenuIds());
        currRole.setMenuIds(new ArrayList<>(menus));
        Set<String> hideMenus = new HashSet<>();
        if (!ObjectUtils.isEmpty(currRole.getHideMenuIds()))
            hideMenus.addAll(currRole.getHideMenuIds());
        if (!ObjectUtils.isEmpty(selfRole.getHideMenuIds()))
            hideMenus.addAll(selfRole.getHideMenuIds());
        currRole.setHideMenuIds(new ArrayList<>(hideMenus));
        Map<String, List<String>> actions = currRole.getActions();
        Map<String, List<String>> selfActions = selfRole.getActions();
        if (actions == null) {
            currRole.setActions(selfActions);
        } else if (selfActions != null) {
            selfActions.forEach((k, v) -> {
                if (actions.containsKey(k)) {
                    if (v != null) {
                        Set<String> actionSet = new HashSet<>();
                        actionSet.addAll(v);
                        actionSet.addAll(actions.getOrDefault(k, new ArrayList<>()));
                        actions.put(k, new ArrayList<>(actionSet));
                    }
                } else {
                    actions.put(k, v);
                }
            });
        }
        return currRole;
    }

    @Override
    public void delete(MenuVO vo) throws BusinessException {
        deleteById(vo.getId());

    }

    @Override
    public List<String> fillParentId(List<String> list) throws BusinessException {
        if (CommonUtil.listIsBlank(list)) return list;
        Set<String> set = new HashSet<>();
        MenuVO cond = new MenuVO();
        cond.putComparisonSymbols("id", ComparisonSymbol.in);
        cond.putComparisonSymbols("parentId", ComparisonSymbol.not);
        findParentId(cond, list, set);
        return new ArrayList<>(set);
    }

    /**
     * 自动填充父菜单
     *
     * @param cond
     * @param children
     * @param set
     */
    private void findParentId(MenuVO cond, List<String> children, Set<String> set) {
        set.addAll(children);
        cond.putParamsCondition("id", children);
        List<Menu> menus = baseMongoDao.list(cond, Menu.class, Menu.class);
        List<String> parentIds = menus.stream().map(Menu::getParentId).collect(Collectors.toList());
        if (!parentIds.isEmpty()) findParentId(cond, parentIds, set);
    }


    private void filterMenu(List<MenuVO> menuList, Role currRole, Boolean isAdmin, String adminTenantId) throws BusinessException {
        int size = menuList.size();
        boolean admin = (isAdmin != null && isAdmin);
        if (currRole == null) {
            for (MenuVO menuVO : menuList) {
//                WebPageVO webPage = menuVO.getWebPage();
//                if (null != webPage) {
//                    webPage = webPageService.queryById(webPage.getId());
//                    menuVO.setWebPage(webPage);
//                }
                List<MenuVO> children = menuVO.getChildren();
                if (!ObjectUtils.isEmpty(children)) {
                    filterMenu(children, currRole, isAdmin, adminTenantId);
//                    children.forEach(m -> m.setIsPrivate(menuVO.getIsPrivate()));
                }
            }
        } else {
            List<String> menuIds = currRole.getMenuIds();
            List<String> hideMenuIds = currRole.getHideMenuIds();
            if (menuIds == null) {
                for (int i = size - 1; i >= 0; i--) {
                    MenuVO menuVO = menuList.get(i);
                    if (admin && adminTenantId.equals(menuVO.getTenantId())) {
//                        WebPageVO webPage = menuVO.getWebPage();
//                        if (null != webPage) {
//                            webPage = webPageService.queryById(webPage.getId());
//                        }
//                        menuVO.setWebPage(webPage);
                        List<MenuVO> children = menuVO.getChildren();
                        if (!ObjectUtils.isEmpty(children)) {
                            filterMenu(children, currRole, isAdmin, adminTenantId);
//                            children.forEach(m -> m.setIsPrivate(menuVO.getIsPrivate()));
                        }
                    } else {
                        menuList.remove(i);
                    }
                }

            } else {
                for (int i = size - 1; i >= 0; i--) {
                    MenuVO menuVO = menuList.get(i);
                    menuVO.setIsHide(!ObjectUtils.isEmpty(hideMenuIds) && hideMenuIds.contains(menuVO.getId()));
                    WebPageVO webPage = menuVO.getWebPage();
//                    if (null != webPage) {
//                        webPage = webPageService.queryById(webPage.getId());
//                    }
                    if (!admin || !adminTenantId.equals(menuVO.getTenantId())) {
                        if (!menuIds.contains(menuVO.getId())) {
                            menuList.remove(i);
                            continue;
                        }
                        if (null != webPage) {
                            List<ButtonVO> buttons = webPage.getButtons();
                            if (buttons != null) {
                                Map<String, List<String>> actions = currRole.getActions();
                                if (actions != null) {
                                    List<String> buttonCodes = actions.getOrDefault("M" + menuVO.getId(), new ArrayList<>());
                                    for (int j = buttons.size() - 1; j >= 0; j--) {
                                        ButtonVO b = buttons.get(j);
                                        if (!buttonCodes.contains(b.getCode())) {
                                            buttons.remove(j);
                                        }
                                    }
                                } else {
                                    buttons.clear();
                                }
                            }
                        }
                    }
//                    menuVO.setWebPage(webPage);
                    List<MenuVO> children = menuVO.getChildren();
                    if (!ObjectUtils.isEmpty(children)) {
                        filterMenu(children, currRole, isAdmin, adminTenantId);
//                        children.forEach(m -> m.setIsPrivate(menuVO.getIsPrivate()));
                    }
                }
            }

        }

    }

}
